<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>自定义卡片节点</title>
</head>
<body>
  <div id="mountNode"></div>
  <script src="../build/g6.js"></script>
  <script>
    const ERROR_COLOR = "#F5222D";
    const getNodeConfig = node => {
      if (node.nodeError) {
        return {
          basicColor: ERROR_COLOR,
          fontColor: "#FFF",
          borderColor: ERROR_COLOR,
          bgColor: "#E66A6C"
        };
      }
      let config = {
        basicColor: "#722ED1",
        fontColor: "#722ED1",
        borderColor: "#722ED1",
        bgColor: "#F6EDFC"
      };
      switch (node.type) {
        case "root": {
          config = {
            basicColor: "#E3E6E8",
            fontColor: "rgba(0,0,0,0.85)",
            borderColor: "#E3E6E8",
            bgColor: "#F7F9FA"
          };
          break;
        }
        default:
          break;
      }
      return config;
    };
    
    const COLLAPSE_ICON = function COLLAPSE_ICON(x, y, r) {
      return [
        ["M", x - r, y],
        ["a", r, r, 0, 1, 0, r * 2, 0],
        ["a", r, r, 0, 1, 0, -r * 2, 0],
        ["M", x - r + 4, y],
        ["L", x - r + 2 * r - 4, y]
      ];
    };
    const EXPAND_ICON = function EXPAND_ICON(x, y, r) {
      return [
        ["M", x - r, y],
        ["a", r, r, 0, 1, 0, r * 2, 0],
        ["a", r, r, 0, 1, 0, -r * 2, 0],
        ["M", x - r + 4, y],
        ["L", x - r + 2 * r - 4, y],
        ["M", x - r + r, y - r + 4],
        ["L", x, y + r - 4]
      ];
    };
    
    const nodeBasicMethod = {
      createNodeBox: (group, config, width, height, isRoot) => {
        /* 最外面的大矩形 */
        const container = group.addShape("rect", {
          attrs: {
            x: 0,
            y: 0,
            width,
            height
          }
        });
        if (!isRoot) {
          /* 左边的小圆点 */
          group.addShape("circle", {
            attrs: {
              x: 3,
              y: height / 2,
              r: 6,
              fill: config.basicColor
            }
          });
        }
        /* 矩形 */
        group.addShape("rect", {
          attrs: {
            x: 3,
            y: 0,
            width: width - 19,
            height,
            fill: config.bgColor,
            stroke: config.borderColor,
            radius: 2,
            cursor: "pointer"
          }
        });

        /* 左边的粗线 */
        group.addShape("rect", {
          attrs: {
            x: 3,
            y: 0,
            width: 3,
            height,
            fill: config.basicColor,
            radius: 1.5
          }
        });
        return container;
      },
      /* 生成树上的 marker */
      createNodeMarker: (group, collapsed, x, y) => {
        group.addShape("circle", {
          attrs: {
            x,
            y,
            r: 13,
            fill: "rgba(47, 84, 235, 0.05)",
            opacity: 0,
            zIndex: -2
          },
          className: "collapse-icon-bg"
        });
        group.addShape("marker", {
          attrs: {
            x,
            y,
            radius: 7,
            symbol: collapsed ? EXPAND_ICON : COLLAPSE_ICON,
            stroke: "rgba(0,0,0,0.25)",
            fill: "rgba(0,0,0,0)",
            lineWidth: 1,
            cursor: "pointer"
          },
          className: "collapse-icon"
        });
      },
      afterDraw: (cfg, group) => {
        /* 操作 marker 的背景色显示隐藏 */
        const icon = group.findByClassName("collapse-icon");
        if (icon) {
          const bg = group.findByClassName("collapse-icon-bg");
          icon.on("mouseenter", () => {
            bg.attr("opacity", 1);
            graph.get("canvas").draw();
          });
          icon.on("mouseleave", () => {
            bg.attr("opacity", 0);
            graph.get("canvas").draw();
          });
        }
        /* ip 显示 */
        const ipBox = group.findByClassName("ip-box");
        if (ipBox) {
          /* ip 复制的几个元素 */
          const ipLine = group.findByClassName("ip-cp-line");
          const ipBG = group.findByClassName("ip-cp-bg");
          const ipIcon = group.findByClassName("ip-cp-icon");
          const ipCPBox = group.findByClassName("ip-cp-box");

          const onMouseEnter = () => {
            this.ipHideTimer && clearTimeout(this.ipHideTimer);
            ipLine.attr("opacity", 1);
            ipBG.attr("opacity", 1);
            ipIcon.attr("opacity", 1);
            graph.get("canvas").draw();
          };
          const onMouseLeave = () => {
            this.ipHideTimer = setTimeout(() => {
              ipLine.attr("opacity", 0);
              ipBG.attr("opacity", 0);
              ipIcon.attr("opacity", 0);
              graph.get("canvas").draw();
            }, 100);
          };
          ipBox.on("mouseenter", () => {
            onMouseEnter();
          });
          ipBox.on("mouseleave", () => {
            onMouseLeave();
          });
          ipCPBox.on("mouseenter", () => {
            onMouseEnter();
          });
          ipCPBox.on("mouseleave", () => {
            onMouseLeave();
          });
          ipCPBox.on("click", () => {});
        }
      },
      setState: (name, value, item) => {
        const hasOpacityClass = [
          "ip-cp-line",
          "ip-cp-bg",
          "ip-cp-icon",
          "ip-cp-box",
          "ip-box",
          "collapse-icon-bg"
        ];
        const group = item.getContainer();
        const childrens = group.get("children");
        graph.setAutoPaint(false);
        if (name === "emptiness") {
          if (value) {
            childrens.forEach(shape => {
              if (hasOpacityClass.indexOf(shape.get("className")) > -1) {
                return;
              }
              shape.attr("opacity", 0.4);
            });
          } else {
            childrens.forEach(shape => {
              if (hasOpacityClass.indexOf(shape.get("className")) > -1) {
                return;
              }
              shape.attr("opacity", 1);
            });
          }
        }
        graph.setAutoPaint(true);
      }
    };

    /**
       * 计算字符串的长度
       * @param {string} str 指定的字符串
       */
       const calcStrLen = (str) => {
        let len = 0;
        for (let i = 0; i < str.length; i++) {
          if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {
            len++;
          } else {
            len += 2;
          }
        }
        return len;
      }
    
    G6.registerNode(
      'card-node',
      {
        drawShape: (cfg, group) => {
          const config = getNodeConfig(cfg);
          const isRoot = cfg.type === "root";
          const nodeError = cfg.nodeError;
          /* 最外面的大矩形 */
          const container = nodeBasicMethod.createNodeBox(
            group,
            config,
            243,
            64,
            isRoot
          );

          if (cfg.type !== "root") {
            /* 上边的 type */
            group.addShape("text", {
              attrs: {
                text: cfg.type,
                x: 3,
                y: -10,
                fontSize: 12,
                textAlign: "left",
                textBaseline: "middle",
                fill: "rgba(0,0,0,0.65)"
              }
            });
          }

          let ipWidth = 0;
          if (cfg.ip) {
            /* ip start */
            /* ipBox */
            const ipRect = group.addShape("rect", {
              attrs: {
                fill: nodeError ? null : "#FFF",
                stroke: nodeError ? "rgba(255,255,255,0.65)" : null,
                radius: 2,
                cursor: "pointer"
              }
            });

            /* ip */
            const ipText = group.addShape("text", {
              attrs: {
                text: cfg.ip,
                x: 0,
                y: 19,
                fontSize: 12,
                textAlign: "left",
                textBaseline: "middle",
                fill: nodeError ? "rgba(255,255,255,0.85)" : "rgba(0,0,0,0.65)",
                cursor: "pointer"
              }
            });

            const ipBBox = ipText.getBBox();
            /* ip 的文字总是距离右边 12px */
            ipText.attr({
              x: 224 - 12 - ipBBox.width
            });
            /* ipBox */
            ipRect.attr({
              x: 224 - 12 - ipBBox.width - 4,
              y: ipBBox.minY - 5,
              width: ipBBox.width + 8,
              height: ipBBox.height + 10
            });

            /* 在 IP 元素上面覆盖一层透明层，方便监听 hover 事件 */
            group.addShape("rect", {
              attrs: {
                stroke: "",
                cursor: "pointer",
                x: 224 - 12 - ipBBox.width - 4,
                y: ipBBox.minY - 5,
                width: ipBBox.width + 8,
                height: ipBBox.height + 10,
                fill: "#fff",
                opacity: 0
              },
              className: "ip-box"
            });

            /* copyIpLine */
            group.addShape("rect", {
              attrs: {
                x: 194,
                y: 7,
                width: 1,
                height: 24,
                fill: "#E3E6E8",
                opacity: 0
              },
              className: "ip-cp-line"
            });
            /* copyIpBG */
            group.addShape("rect", {
              attrs: {
                x: 195,
                y: 8,
                width: 22,
                height: 22,
                fill: "#FFF",
                cursor: "pointer",
                opacity: 0
              },
              className: "ip-cp-bg"
            });
            /* copyIpIcon */
            group.addShape("image", {
              attrs: {
                x: 200,
                y: 13,
                height: 12,
                width: 10,
                img: "https://os.alipayobjects.com/rmsportal/DFhnQEhHyPjSGYW.png",
                cursor: "pointer",
                opacity: 0
              },
              className: "ip-cp-icon"
            });
            /* 放一个透明的矩形在 icon 区域上，方便监听点击 */
            group.addShape("rect", {
              attrs: {
                x: 195,
                y: 8,
                width: 22,
                height: 22,
                fill: "#FFF",
                cursor: "pointer",
                opacity: 0
              },
              className: "ip-cp-box",
              tooltip: "复制IP"
            });

            const ipRectBBox = ipRect.getBBox();
            ipWidth = ipRectBBox.width;
            /* ip end */
          }

          /* name */
          const nameText = group.addShape("text", {
            attrs: {
              text: cfg.name,
              x: 19,
              y: 19,
              fontSize: 14,
              fontWeight: 700,
              textAlign: "left",
              textBaseline: "middle",
              fill: config.fontColor,
              cursor: "pointer"
            }
            // tooltip: cfg.name,
          });

          /* 下面的文字 */
          const remarkText = group.addShape("text", {
            attrs: {
              text: cfg.keyInfo,
              x: 19,
              y: 45,
              fontSize: 14,
              textAlign: "left",
              textBaseline: "middle",
              fill: config.fontColor,
              cursor: "pointer"
            }
          });

          if (nodeError) {
            group.addShape("text", {
              attrs: {
                x: 191,
                y: 62,
                text: '⚠️',
                fill: '#000',
                fontSize: 18
              }
            });
          }

          const hasChildren = cfg.children && cfg.children.length > 0;
          if (hasChildren) {
            nodeBasicMethod.createNodeMarker(group, cfg.collapsed, 236, 32);
          }
          return container;
        },
        afterDraw: nodeBasicMethod.afterDraw,
        setState: nodeBasicMethod.setState
      },
      "single-shape"
    );  

    const data = {
      nodes: [
        {
          name: 'cardNodeApp',
          ip: '127.0.0.1',
          nodeError: true,
          type: 'root',
          keyInfo: 'this is a card node info',
          x: 100,
          y: 50
        },
        {
          name: 'cardNodeApp',
          ip: '127.0.0.1',
          nodeError: false,
          type: 'subRoot',
          keyInfo: 'this is sub root',
          x: 100,
          y: 150
        },
        {
          name: 'cardNodeApp',
          ip: '127.0.0.1',
          nodeError: false,
          type: 'subRoot',
          keyInfo: 'this is sub root',
          x: 100,
          y: 250,
          children: [
            {
              name: 'sub'
            }
          ]
        }
      ],
      edges: []
    }

    const graph = new G6.Graph({
      container: 'mountNode',
      width: 800,
      height: 600,
      defaultNode: {
        shape: 'card-node'
      }
    })

    graph.data(data)
    graph.render()
  </script>
</body>
</html>